package com.hzit.search.service.impl;

import cn.hutool.core.util.StrUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Buckets;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.hzit.search.entity.ItemEntity;
import com.hzit.search.entity.ItemVo;
import com.hzit.search.service.ItemSearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 类名：
 * 作者：WF
 * 功能：
 */
@Service
public class ItemSearchServiceImpl implements ItemSearchService {
	@Autowired
	private ElasticsearchClient client;
	@Autowired
	private StringRedisTemplate redisTemplate;

	//1. 开始进行查询
	@Override
	public Map search(ItemVo vo) throws IOException {
		System.out.println("vo = " + vo);
		//1.1 定义返回结果的map
		Map resultMap = new HashMap();
		//1.2 定义返回查询到的sku商品列表
		List<ItemEntity> itemList = new ArrayList<>();
		//1.3 定义返回的分类的集合
		List<String> categoyList = new ArrayList<>();
		//1.4 根据 vo.getKeywords（） 是否有值确定request的值(换言之，如果没有输入关键字，则需要查询所有的匹配记录,否则，要根据条件查询)
		SearchRequest request = null;
		//1.5 获取分页参数
		int page = vo.getPage();                // 当前页
		int pageSize = vo.getPageSize();        // 每页大小
		//1.6.1 判断是否关键字有值
		if(StrUtil.isNotBlank(vo.getKeywords())){
			//1.6.2 定义过滤查询集合
			List<Query> filterQuery = new ArrayList<>();
			//1.6.3 定义排序操作的集合
			List<SortOptions> sortList = new ArrayList<>();

			//过滤查询一： 进行多字段匹配查询
			//1.1 得到多字段查询对象
			Query matchQuery = MultiMatchQuery.of(mmq -> mmq.fields("title", "brand", "category").query(vo.getKeywords()))._toQuery();
			//1.2 将多字段查询对象添加到查询对象集合中
			filterQuery.add(matchQuery);

			//过滤查询二：进行分类查询
			if(StrUtil.isNotBlank(vo.getCategory())){
				//2.1 得到分类查询对象
				Query categoryQuery = TermQuery.of(tq -> tq.field("category").value(vo.getCategory()))._toQuery();
				//2.2 将分类查询对象放到查询对象集合中
				filterQuery.add(categoryQuery);
			}

			// 过滤查询三：进行品牌查询
			if(StrUtil.isNotBlank(vo.getBrand())){
				//3.1 得到品牌查询对象
				Query brandQuery = TermQuery.of(tq -> tq.field("brand").value(vo.getBrand()))._toQuery();
				//3.2 将品牌放到过滤查询对象集合中
				filterQuery.add(brandQuery);
			}
			// 过滤查询四：进行规格查询
			if(vo.getSpec() != null && vo.getSpec().size() > 0){
				List<String> spec = vo.getSpec();
				for (String s : spec) {
					// 4.1 拆分规格及规格选项
					String[] split = s.split(":");  // split[0]: 规格名字 split[1]: 规格选项的值
					// 4.2 构造规格查询对象
					Query specQuery = TermQuery.of(tq -> tq.field("specMap." + split[0] + ".keyword").value(split[1]))._toQuery();
					// 4.3 将规格查询对象放到查询对象集合列表中
					filterQuery.add(specQuery);
				}
			}
			// 过滤查询五：进行价格区间查询
			if(StrUtil.isNotBlank(vo.getPrice())){
				String price = vo.getPrice();
				//5.1 拆分价格区间
				String[] split = price.split("-");      // split[0]: 最小值 split[1]: 最大值
				//5.2 定义RangeQuery对象进行价格区间查询
				if(split[1].equals("*")){
					//5.2.1 这是一个开始的价格区间query对象
					Query queryRange = RangeQuery.of(rq -> rq.field("price").gte(JsonData.of(split[0])))._toQuery();
					//5.2.2 添加到过滤查询对象列表中
					filterQuery.add(queryRange);
				}else{
					Query queryRange = RangeQuery.of(rq -> rq.field("price").gte(JsonData.of(split[0])).lte(JsonData.of(split[1])))._toQuery();
					filterQuery.add(queryRange);
				}
			}
			// 进行排序查询
			if(StrUtil.isNotBlank(vo.getSort())){
				String sort = vo.getSort();
				//5.1 进行拆分
				String[] split = sort.split(":");       // split[0]: 排序字段 split[1]: 排序的升降序 asc：升 des：降
				//5.2 根据传入的排序参数确定是升序还是降序排序
				if(split[1].equals("asc")){
					SortOptions sortAsc = SortOptions.of(so -> so.field(sf -> sf.field(split[0]).order(SortOrder.Asc)));
					sortList.add(sortAsc);
				}else if(split[1].equals("desc")){
					SortOptions sortDesc = SortOptions.of(so -> so.field(sf -> sf.field(split[0]).order(SortOrder.Desc)));
					sortList.add(sortDesc);
				}
			}
			// 构造SearchRequest对象
			request = SearchRequest.of(sr -> sr.index("item")
												// ① 过滤查询（多字段匹配查询、分类、品牌、规格、价格区间）
												.query(q->q.bool(qb->qb.filter(filterQuery)))
												// ② 高亮查询
												.highlight(hl->hl.preTags("<span style='color:red'>").postTags("</span>").fields("title",hlf->hlf))
												// ③ 为category进行聚合
												.aggregations("aggCategory",gc->gc.terms(t->t.field("category")))
												// ④ 进行排序查询
												.sort(sortList)
												// ⑤ 从哪条记录开始
												.from((page-1)*pageSize)
												// ⑥ 每页显示记录条数
												.size(pageSize)
			);
		}else{
			request = SearchRequest.of(sr->sr.index("item")
											  .query(q->q.matchAll(ma->ma)));
		}
		//1.7 返回结果响应结果对象
		SearchResponse<ItemEntity> searchResponse = client.search(request, ItemEntity.class);
		//1.7.1 处理返回的结果对象
		HitsMetadata<ItemEntity> hits = searchResponse.hits();
		//1.7.2 得到总记录数
		long total = hits.total().value();
		//1.7.3 计算总页数
		int totalPage = (int) Math.ceil(total/(double)pageSize);
		System.out.println("total:" + total);
		//1.7.4 得到所有的聚合数据
		Map<String, Aggregate> aggregations = searchResponse.aggregations();
		//1.7.5 得到我们指定的聚合分组数据
		Aggregate aggCategory = aggregations.get("aggCategory");
		//1.7.6 处理高亮数据
		for (Hit<ItemEntity> hit : hits.hits()) {
			//① 得到原始数据
			ItemEntity source = hit.source();
			//② 取得高亮查询的结果
			Map<String, List<String>> highlight = hit.highlight();
			List<String> highlightValue = highlight.get("title");
			//③ 处理得到的高亮字段的值
			if(!CollectionUtils.isEmpty(highlightValue) && highlightValue.size() > 0){
				//3.1 将高亮字段的值覆盖原来的source对象中的title属性的值
				source.setTitle(highlightValue.get(0));
			}
			itemList.add(source);
		}
		//1.8 处理分组数据
		if(aggCategory != null){
			Buckets<StringTermsBucket> buckets = aggCategory.sterms().buckets();
			for (StringTermsBucket termsBucket : buckets.array()) {
				//1.8.1 得到分组的数据
				String key = termsBucket.key();
				//1.8.2 将当前的分类添加到分类列表中
				categoyList.add(key);
			}
		}

		//1.9 根据分类查询前端的品牌及规格列表
		// 思想：
		// ① 如果前端没有传值，并且此时categoyList分类列表有超过一个值，我们可以取得categoyList中的第一个值作为默认分类
		// ② 如果前端传入了一个分类，就使用此分类作为查询分类
		String category = vo.getCategory();
		String cate = "";
		if(StrUtil.isNotBlank(category)){
			cate = category;
		}else if(categoyList != null && categoyList.size() > 0){
			cate = categoyList.get(0);
		}
		//1.10 根据分类查询中品牌列表及规格列表
		Map brandAndSpecList = findBandAndSpecByCategory(cate);


		//1.11 将查询到的sku商品列表放到resultMap
		resultMap.put("itemList",itemList);
		//1.12 将分类列表添加到resultMap中
		resultMap.put("categoryList",categoyList);
		//1.13 将上面的规格及品牌列表放到resultMap中
		resultMap.putAll(brandAndSpecList);
		//1.14 将总记录数及总页数放入map中
		resultMap.put("total",total);
		resultMap.put("totalPage",totalPage);

		// 最后返回
		return resultMap;
	}

	// 2. 通过分类查询品牌及规格列表
	private Map findBandAndSpecByCategory(String cate) {
		Map brandAndSpecMap = new HashMap();
		if(StrUtil.isNotBlank(cate)){
			//2.1 根据分类名称得到模板id
			HashOperations<String, String, String> hashOperations = redisTemplate.opsForHash();
			String typeId = hashOperations.get("itemCats", cate);
			//2.2 根据模板id，找到品牌列表
			String brands = hashOperations.get("brandList", typeId);
			List<Map> brandList = JSON.parseArray(brands, Map.class);
			//2.3 根据模板id，找到规格列表
			String specs = hashOperations.get("specList", typeId);
			List<Map> specList = JSON.parseArray(specs, Map.class);
			//2.4 将品牌及规格列表放到map另
			brandAndSpecMap.put("brandList",brandList);
			brandAndSpecMap.put("specList",specList);
		}else{
			brandAndSpecMap.put("brandList",new ArrayList<>());
			brandAndSpecMap.put("specList",new ArrayList<>());
		}

		//2.5 返回map集合
		return brandAndSpecMap;
	}
}
